package cn.uway.shiro;


import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.session.MapSessionRepository;

import cn.uway.cache.LocalCache;
import cn.uway.pojo.demo.UserInfo;
import cn.uway.properties.ShiroProperties;
import cn.uway.service.demo.DemoService;
import cn.uway.utils.MD5Encoder;
import cn.uway.utils.PubValue;
import cn.uway.utils.SpringSecurityUtils;


public class DatabaseRealm extends AuthorizingRealm {
	
	private Logger logger = LoggerFactory.getLogger(DatabaseRealm.class);
	
	//获取配置信息
	@Autowired
	private  ShiroProperties shiroProperties;
	
	@Value("${spring.session.maxInactiveIntervalInSeconds}")
	private  Integer  maxInactiveIntervalInSeconds;
	
	
	@Autowired
	private  LocalCache  ehCache;
	
	@Autowired
	private DemoService demoService;   
	
	@Autowired
	private  MapSessionRepository sessionRepository;
	
		
	private static final String passwordRetryCacheName = "passwordRetryCache";
	
		
	public DatabaseRealm(HashedCredentialsMatcher credentialsMatcher){
		super();  
		this.setCredentialsMatcher(credentialsMatcher);
	}

	/**
	 * 认证回调函数,登录时调用.
	 */
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {
		
		System.out.println("==============accountLockedSeconds="+shiroProperties.getAccountLockedSeconds());
		
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		Object object = authcToken.getCredentials();
		String md5Pwd = MD5Encoder.encode( String.valueOf((char[]) object)  );
		token.setPassword(md5Pwd.toCharArray());
        String userName = (String)token.getPrincipal();  //得到用户名   
        AtomicInteger retryCount = (AtomicInteger) ehCache.getObject(passwordRetryCacheName,userName);
		if (retryCount == null) {
			retryCount = new  AtomicInteger(0);
			ehCache.putObjectWithExpireTime(passwordRetryCacheName,userName, retryCount, shiroProperties.getAccountLockedSeconds());
		}
		int errorTimes = retryCount.incrementAndGet();
		if (errorTimes > 6) {
			logger.warn("==========密码重试错误次数超过{}次", 6);
			throw new ExcessiveAttemptsException("密码重试错误次数超过6次");			
		}		
        try {
        	Map<String, Object> map = demoService.loginCheck(userName, md5Pwd);  		
    		if(PubValue.RET_CODE_SUCCESS.equals(map.get(PubValue.RET_CODE_KEY)) ){  
    			String userId = null;
    			if(map.containsKey(PubValue.RET_DATA_KEY)){
    				UserInfo user = (UserInfo)map.get(PubValue.RET_DATA_KEY);
    				userId = user.getUserId();
    				logger.info("=============RET_DATA  userId ="+userId);    				
    			}
    			
    			String sessionId = SecurityUtils.getSubject().getSession().getId().toString();
    			
    			//不同账号，同一session(浏览器)登录，后者踢掉前者，清理其缓存数据
    			checkAndKickOffAnotherUser(userId);    			
				//同一账号，不同浏览器窗口登录，将前一个登录者踢下线 
				checkAndKickOffSameUser(userId, sessionId);    
				
				ehCache.removeObject(passwordRetryCacheName,userName);
    			
    			String loginId = MD5Encoder.encode(userId);
    			
    			UserInfo userInfo = new UserInfo();
    			userInfo.setUserId(userId);
    			userInfo.setLoginName(userName);
    			userInfo.setPassword(md5Pwd);  
    			userInfo.setLoginId(loginId);
    			
    			SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(userName,
    					SpringSecurityUtils.generatePassword(userInfo.getLoginName(),userInfo.getPassword() ),  getName() );
				sai.setCredentialsSalt(ByteSource.Util.bytes(userName));
				
				//将用户信息放到shiro的session中，shiro的session会和HttpSession共享数据
				setSession(PubValue.SESSIONKEY_USER_INFO,userInfo);	
				setSession(PubValue.SESSIONKEY_LOGIN_ID,loginId); //登录TOKEN，用于解决同一浏览器不同tab页里面登录多个不同账号sessionID一样的问题，后一个账号踢掉前一个账号
				
				logger.info("=============login   sessionId ="+SecurityUtils.getSubject().getSession().getId());
    			//测试往ehcache缓存中存放数据
    			ehCache.putObjectWithExpireTime("test.Key", userInfo, 2*60);
    			return sai;
    		}
    		else{    			
    			ehCache.putObjectWithExpireTime(passwordRetryCacheName, userName, retryCount, shiroProperties.getAccountLockedSeconds());   			
    			throw new AccountException("账户名或密码错误");
    		}			
		}
        catch (AccountException e) {
			throw e;
		}
        catch (Exception e) {
			logger.debug("shiro security auth exception. ", e);
			throw new AccountException(e.getMessage(), e);
		}
	}

	/**
	 * 重写获取用户权限的方法,获取用户角色和菜单权限
	 */
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		
		// 获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next();
		String currentUsername = (String) super.getAvailablePrincipal(principals);
		logger.info("AuthorizationInfo userName = {}", currentUsername);
		UserInfo userInfo = SpringSecurityUtils.getCurrentUser();	
		if(userInfo!=null){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  	
			Set<String> roleSet = new HashSet<String>();
			roleSet.add("管理员");
			info.setRoles(roleSet);
			info.addStringPermission("/welcome");					
            return info;
		}
		else{
			return null;
		}		
	}

	/**
	 * 将一些数据放到ShiroSession中,以便于其它地方使用 使用时直接用HttpSession.getAttribute(key)就可以取到	 * 
	 * @param key
	 * @param value
	 */
	private void setSession(String key, Object value) {
		Subject currentUser = SecurityUtils.getSubject();
		if (currentUser !=null ) {
			Session session = currentUser.getSession();
			logger.info("Session默认超时时间为 [{}]毫秒", session.getTimeout());
			if ( session !=null ) {
				session.setAttribute(key, value);
			}
		}
	}

	/**
	 * 更新用户授权信息缓存.
	 */
	public void clearCachedAuthorizationInfo(String principal) {
		SimplePrincipalCollection principals = new SimplePrincipalCollection(
				principal, getName());
		clearCachedAuthorizationInfo(principals);
	}

	/**
	 * 清除所有用户授权信息缓存.
	 */
	public void clearAllCachedAuthorizationInfo() {
		Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
		if (cache != null) {
			for (Object key : cache.keys()) {
				cache.remove(key);
			}
		}
	}
	
	
	/**
	 * 不同账号，同一session(浏览器)登录，后者踢掉前者，清理其缓存数据
	 * @param userId
	 */
	public void checkAndKickOffAnotherUser(String userId){
		try{
			Session session = SecurityUtils.getSubject().getSession();
			UserInfo userInfo = (UserInfo)session.getAttribute(PubValue.SESSIONKEY_USER_INFO); 
			if(userInfo!=null && !userInfo.getUserId().equals(userId)){ //账户已登录且关联的sessionId不一样
				System.out.println("==============removeObject  key = "+PubValue.CACHE_PREFIX_SINGLE_LOGIN+userInfo.getUserId());
				ehCache.removeObject(PubValue.CACHE_PREFIX_SINGLE_LOGIN+userInfo.getUserId());
			}
		}
		catch(Exception e){
			logger.error("操作ehcache缓存出错",e);
		}
		
	}
	
	
	/**
	 * 同一账号，不同session(浏览器)登录，后者踢掉前者
	 * @param userId
	 * @param sessionId
	 */
	public void checkAndKickOffSameUser(String userId, String sessionId){
		try{
			String cachedSessionId = (String)ehCache.getObject(PubValue.CACHE_PREFIX_SINGLE_LOGIN+userId);
			if(cachedSessionId!=null && !cachedSessionId.equals(sessionId)){ //账户已登录且关联的sessionId不一样
				clearSessionDataBySessionId(cachedSessionId);
			}
			ehCache.putObjectWithExpireTime(PubValue.CACHE_PREFIX_SINGLE_LOGIN+userId, sessionId, maxInactiveIntervalInSeconds);
		}
		catch(Exception e){
			logger.error("操作ehcache缓存出错",e);
		}		
	}	  
	
		
	private void clearSessionDataBySessionId(String sessionId){
			
		sessionRepository.delete(sessionId);
	} 

}